let _Vue = null
/**
 * Vue 的构建
 *  Vue 的构建版本包含运行时版本和完整版
 *    运行时版：不支持 template 模板，需要打包的时候提前编译，把 tamplate 编译成 render 函数
 *    完整版：包含运行时和编译器，体积比运行时版大 10K 左右，程序运行的时候编译器把模板转换成 render 函数
 */
export default class VueRouter {
  /**
   * 二、构造函数
   *    在构造函数中需要初始化三个属性
   *    options
   *    data: 响应式的对象，存储当前的路由地址，当路由变化的时候，要自动下载组件
   *    routeMap: 解析 options 参数中的路由规则 routes 存储到 routeMap 对象中，对象中是键值对，键是路由的地址，值是路由组件
   *              会根据路由地址找到组件渲染到页面上
   * @param {*} options
   * @returns {VueRouter}
   */
  constructor (options) {
    this.options = options
    this.routeMap = {}
    // 通过 Vue 的 observable 方法，创建响应式的对象。这个对象可以使用在渲染函数或计算属性中
    this.data = _Vue.observable({
      current: '/'
    })
  }

  /**
   * 一、创建静态方法 install() 会有两个参数
   * 第一个是 Vue 的构造函数
   * 第二个是可选的选项对象
   */
  static install (Vue) {
    // 1、判断插件是否安装，如果已经被安装就不需要重复安装
    if (VueRouter.install.installed) {
      // 如果插件已经安装，就直接 return
      // 否则就往下执行
      return
    }
    VueRouter.install.installed = true
    /**
     * 2、把 Vue 的构造函数记录到全局变量中
     *    因为 install() 是一个静态方法，会接收 Vue 的构造函数，方便创建组件的时候使用
     *    如创建 router-view、router-link 的两个组件时会调用 Vue.component()
     *    所以需要把 Vue 的构造函数记录到全局的变量中
     */
    _Vue = Vue
    /**
     * 3、把创建 Vue 实例时传入的 router 对象注入到所有的实例上
     *    this.$router 就是在这个时候注入到实例上
     */
    // 混入
    _Vue.mixin({
      // 获取 Vue 实例，给 _Vue 的原型上添加 $router 属性
      beforeCreate () {
        /**
         * 所有的 Vue 实例都会有这个混入的选项
         * 当执行的时候就会执行这个方法很多次
         * 但是挂载 $router 属性只需要执行一次
         * 所以就需要去判断，当前实例的 $options 中是否有 router 属性
         * 因为只有 Vue.$options 的选项中才有 router 属性，而组件是没有的
         */
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  /**
   * 四、包装 createRouteMap 和 initComponents 方法，方便调用 install() 方法的时候使用
   */
  init () {
    this.createRouteMap()
    this.initComponents(_Vue)
    this.initEvent()
  }

  /**
   * 五
   *    当历史记录发生变化的时候，页面也要根据地址的变化而重新渲染
   *    可以使用 popState
   */
  initEvent () {
    // 注册 popstate 事件
    window.addEventListener('popstate', () => {
      this.data.current = window.location.pathname
    })
  }

  /**
   * 三、createRouteMap
   *    createRouteMap 方法的作用是把构造函数中传过来的路由规则 routes 转换成键值对的形式存储到 routeMap 中
   */
  createRouteMap () {
    // 遍历路由规则，并解析到 routeMap 对象中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    })
  }

  /**
   * 四、初始化 router 组件
   * @param {*} Vue Vue 实例
   */
  initComponents (Vue) {
    // 注册 route-link 组件
    Vue.component('router-link', {
      props: {
        to: String
      },
      // 运行时版不支持 template
      // template: '<a :href="to"><slot></slot></a>'

      // render 函数是一个渲染函数，会有一个 h 的函数参数
      // h 函数的作用是创建虚拟 DOM 在 render 函数中调用 h 函数，并把调用 h 函数的结果返回
      // h 函数是 Vue 传进来的
      render (h) {
        // 第一个参数是选择器，可以传入想要创建的标签名
        // 第二个参数是传入标签上想要设置的属性，如果传入的是 DOM 的标签，就要添加 attrs 属性，代表是 DOM 标签
        // 第三个参数是传入生成元素的子元素，以数组的形式
        return h('a', {
          attrs: {
            href: this.to
          },
          // 注册 a 标签的点击事件
          on: {
            click: this.clickHandler
          }
        }, [this.$slots.default])
      },
      methods: {
        /**
         * 在单页面中，需要控制点击跳转的时候不会去刷新页面，而是改变浏览器的地址和渲染对应的页面
         * @param {*} e
         */
        clickHandler (e) {
          /**
           * history.pushState 方法有三个参数
           *  data:Object 触发 pushState 时，传给 pushState 的事件对象
           *  title:String 网页标题
           *  url:String 跳转的页面地址
           */
          history.pushState({}, '', this.to)
          // 加载当前路径的页面
          this.$router.data.current = this.to
          // 阻止默认行为
          e.preventDefault()
        }
      }
    })
    const _self = this
    // 注册 router-view 组件
    Vue.component('router-view', {
      // 获取当前地址的路由组件
      render (h) {
        // 找到当前路由的地址，根据地址去 routeMap 获取对应的组件
        const component = _self.routeMap[_self.data.current]
        // 然后调用 h 函数，转换成虚拟 DOM 返回
        return h(component)
      }
    })
  }
}
